Skip to content

Object-oriented refactoring of active stress#578

Draft
michelebucelli wants to merge 23 commits into
SimVascular:mainfrom
michelebucelli:feature/oo-active-stress
Draft

Object-oriented refactoring of active stress#578
michelebucelli wants to merge 23 commits into
SimVascular:mainfrom
michelebucelli:feature/oo-active-stress

Conversation

@michelebucelli

Copy link
Copy Markdown
Collaborator

Partially addresses #519.

Current situation

  1. Coupling of electrophysiology and mechanics through active stress had been removed in Object oriented refactoring of ionic models and plotting calcium #540, pending this refactoring;
  2. only one active stress model was implemented, was tightly coupled to specific ionic models, and was not accessible through the user-exposed parameters.

Release Notes

Many of the implementation choices here are modeled after the implementation of IonicModel in #540. Here, I took some further steps towards encapsulation which might be ported to IonicModel as well, although maybe in a separate PR. I'll add some comments to the code about this.

  1. The PR introduces the following hierarchy of classes for active stress models, with the idea that new active stress model can be added easily by deriving new classes from ActiveStress or ActiveStressODE.
---
config:
    class:
      hideEmptyMembersBox: true
---
classDiagram
  ActiveStress <|-- ActiveStressODE
  ActiveStressODE <|-- NashPanfilov
  ActiveStress <|-- UniformSteadyActiveStress
  dmnType *-- ActiveStress
  ActiveStressFactory .. ActiveStress : instantiates
  Factory~BaseClass~ <|.. ActiveStressFactory : BaseClass=ActiveStress
Loading
  1. A new abstract Factory class is introduced, by templating the IonicModelFactory; aliases are provided for both Factory<IonicModel> and Factory<ActiveStress>.

  2. The ActiveStress class and derived classes manage their own XML parameters through the abstract class ActiveStressParameters and the methods get_parameters, read_parameters, distribute_parameters, so that the addition of model parameters can be localized to the source files where the model is defined.

  3. A (shared) pointer to ActiveStress is stored in dmnType, to allow different active stress models in different domains.

  4. Time stepping of ActiveStress instances is managed by Integrator::predictor. After advancing one time step, the active tension along the three principal directions is stored in the vector CepMod::cem.Ya_{f,s,n}, whose entries are then passed on to the constitutive law routines.

  5. The XML section for a struct domain now can optionally contain the following tag:

    <ActiveStress>
      <Model>Model name (e.g. NashPanfilov, UniformSteady, ...)</Model>
    
      <Directional_distribution>
        <!-- optional, and same as currently implemented for Fiber_reinforcement_stress -->
      </Directional_distribution>
    
      <UniformSteady>
        <Value>123</Value>
      <UniformSteady>
    
      <!-- other sections for every implemented model, if needed -->
    </ActiveStress>

    If present, the tag enables active stress through the ActiveStress class. If the active stress model is calcium-based, and no electrophysiology equation is present, the calcium will be kept to zero.

Documentation

New additions are documented via Doxygen comments. Documentation for ActiveStress and ActiveStressODE also includes a brief list of steps needed to implement a new model.

Testing

Existing automatic tests pass with the new implementation, although they do not cover the newly added features.

I have tested this on a basic electromechanics example based on cases/cep/slab_domains. I attach below a preview of the simulation.
anim
I plan to turn this into an integration test (running one timestep is reasonably fast).

TODO before marking as ready

  • add integration test for coupled electromechanics

Code of Conduct & Contributing Guidelines

Comment on lines +232 to +233
/// State variables for the model.
Array<double> states;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The state vector for the active stress model is stored into the ActiveStress instance itself, instead of at a more "global" scope as for the Xion state vector of IonicModel.

I think this is a slightly better design, and deals better with heterogeneous models across domain interfaces. Accordingly, I think this would be a good design to adopt for IonicModel too, although perhaps it's better to do it in a follow-up PR.

Comment on lines +238 to +245
/// Active tension coefficient along the fiber direction.
double eta_f;

/// Active tension coefficient along the sheet direction.
double eta_s;

/// Active tension coefficient along the sheet-normal direction.
double eta_n;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dseyler for now these are domain-wide constant, same as in the old code. I think this class would be the perfect place to implement spatially-varying values for these coefficients in a way that makes that feature available to all concrete active stress models.

Comment on lines +9 to +39
/**
* @brief Nash-Panfilov active stress model.
*
* This class implements the Nash-Panfilov active stress model [1], with the
* modifications introduced by Goktepe and Kuhl [2].
*
* The model equations are the following:
* @f[ \begin{aligned}
* \dv{\Tact}{t} &= \varepsilon(\calcium)(
* \eta_\text{T} (\calcium - \calcium_\text{rest}) - \Tact)\;, \\
* \varepsilon(\calcium) &=
* \varepsilon_0 + (\varepsilon_i - \varepsilon_0)
* \exp(-\exp(-\xi_T (\calcium - \calcium_\text{crit})))\;,
* \end{aligned} @f]
* where @f$\eta_\text{T}@f$, @f$\calcium_\text{rest}@f$,
* @f$\calcium_\text{crit}@f$, @f$\varepsilon_0@f$, @f$\varepsilon_i@f$ and
* @f$\xi_T@f$ are user-defined model parameters. The function
* @f$\varepsilon(\calcium)@f$ is a sigmoidal-shaped calcium-dependent time
* constant (see Figure 3 in [2] for more details).
*
* @note The sensitivity of the model to calcium is controlled by the paramter
* @f$\eta_\text{T}@f$, which has the same units of active tension over calcium.
* Therefore, if the ionic model providing the calcium is phenomenological (see
* @ref IonicModel) and calcium is non-dimensional, this parameter may need to
* be rescaled as well. Similar considerations apply to @f$\xi_T@f$.
*
* **References**:
* 1. [Nash, Panfilov (2004)](https://doi.org/10.1016/j.pbiomolbio.2004.01.016)
* 2. [Goktepe, Kuhl (2009)](https://doi.org/10.1007/s00466-009-0434-z)
*/
class NashPanfilov : public ActiveStressODE {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here there's a slight departure from the previous implementation.

In the old code, the Nash-Panfilov model was driven by potential if coupled to the Aliev-Panfilov or Bueno-Orovio models, and by calcium otherwise. I imagine this choice was mostly due to the phenomenological nature of AP and BO, where calcium is not really a variable. However, both models have a variable that resembles calcium transients more closely than the potential does, which they return through their get_calcium_index methods.

I chose to only feed the calcium-like variables into the active stress model, and not the potential, to keep the ActiveStress interface more lean and closer to what makes biological sense. This means that the behavior of Nash-Panfilov coupled to AP or BO will be slightly different (in my opinion slightly more correct).

Notice that, even before #540, the coupling code was effectively dead code, so there's no test case that is affected by this change in behavior.

Comment on lines +11 to +14
// Forward Euler time stepping.
Vector<double> f = getf(t, state, calcium, fiber_stretch, fiber_stretch_rate);

state.add(dt, f);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to at least offer the option of a Runge-Kutta method, but it should be relatively straightforward to do that at a later stage.

Comment on lines +9 to +18
/**
* @brief Uniform and steady active stress model.
*
* Defines an active tension that is constant in space and time, i.e.
* @f[
* \Tact(t, \calcium, \fiberstretch, \fiberstretchrate, \astressstate) = g\;,
* @f]
* where @f$g@f$ is a user-defined constant value.
*/
class UniformSteadyActiveStress : public ActiveStress {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dseyler In my mind, unsteady prescribed active stress can be modeled after this class (of course with a bit of added complications to it, related e.g. to loading from file the time-dependent active tension profile, and possibly the activation times used to shift it).

I think once #577 is merged this should be rather easy.

Comment on lines +1321 to +1330
/// @brief Parameters for a generic active stress model.
///
/// This class is meant to be inherited from to implement parameters for
/// specific active stress models. Derived classes will mostly have to call
/// add_parameter in their constructor to define the model-specific parameters.
///
/// In the XML file, this class, and the classes derived from it, correspond to
/// the element <Model_name> within the <Active_stress> element, where
/// Model_name is the name of a concrete active stress model.
class ActiveStressModelParameters : public ParameterLists {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to IonicModelParameters (original thread), this class might be implementing general-purpose stuff that would probably make more sense in ParameterList. I am still a bit confused about the parameter parsing logic, so I think this might be a sub-optimal solution.

I think similar problems are present in other classes in this file (e.g. there's a few classes redefining the xml_element_name member although they inherit it from ParameterLists), and overall parameter parsing might benefit from a bit of rationalization, but perhaps this is not the right PR for that.

Comment on lines +1900 to +1905
// @todo[michelebucelli] The active tension here was kept to zero,
// so I replicated the same for fibers, sheets and normals. This
// might not be the correct thing to do.
mat_models::compute_pk2cc(com_mod, cep_mod, eq.dmn[cDmn], F, nFn,
fN, /* ya_f = */ 0.0, /* ya_s = */ 0.0,
/* ya_n = */ 0.0, S, Dm, Ja);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does anyone have any insight about this?

Maybe the correct interpretation is that the active stress is kept to zero because this code aims at computing only the passive part of thes stress, but I am not sure about it.

@michelebucelli michelebucelli added the OOP Refactor Object-Oriented Programming Refactor of Code label Jun 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

OOP Refactor Object-Oriented Programming Refactor of Code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant